using System;

namespace PropertyExpression.Common
{
    public struct Option<T>
    {
        private readonly bool hasValue;
        private readonly T value;

        public Option(T value)
        {
            this.value = value;
            hasValue = true;
        }

        public bool HasValue
        {
            get { return hasValue; }
        }

        public T Value
        {
            get
            {
                if (HasValue) return value;
                throw new InvalidOperationException(
                    string.Format("Optional value of '{0}' type has no value.", typeof (T)));
            }
        }

        public TResult Match<TResult>(Func<T, TResult> some, Func<TResult> none)
        {
            return HasValue ? some(Value) : none();
        }

        public void Match(Action<T> some, Action none)
        {
            if (HasValue) some(Value);
            else none();
        }

        public T ValueOrDefault()
        {
            return HasValue ? Value : default(T);
        }

        public T ValueOr(T other)
        {
            return HasValue ? Value : other;
        }

        public override string ToString()
        {
            return HasValue ? Value.ToString() : "None";
        }

        public static implicit operator Option<T>(T value)
        {
            return new Option<T>(value);
        }
    }

    public static class Option
    {
        public static Option<T> AsOption<T>(this T it)
        {
            return new Option<T>(it);
        }

        public static Option<T> ToOption<T>(this T it) where T : class
        {
            return it ?? new Option<T>();
        }

        public static Option<T> ToOption<T>(this T? it) where T : struct 
        {
            return it.HasValue ? it.Value : new Option<T>();
        }

        public static Option<T> None<T>(this T it, Func<T, bool> predicate)
        {
            return predicate(it) ? new Option<T>() : it;
        }

        public static Option<TResult> Select<T, TResult>(this Option<T> it, Func<T, TResult> func)
        {
            return it.HasValue ? func(it.Value) : new Option<TResult>();
        }

        public static void Select<T>(this Option<T> it, Action<T> action)
        {
            if (it.HasValue) action(it.Value);
        }

        public static Option<TResult> SelectMany<T, TResult>(this Option<T> it, Func<T, Option<TResult>> func)
        {
            return it.HasValue ? func(it.Value) : new Option<TResult>();
        }

        public static Option<TResult> SelectMany<T, TOption, TResult>(
            this Option<T> it, Func<T, Option<TOption>> optionFunc, Func<T, TOption, TResult> resultFunc)
        {
            return it.SelectMany(value1 => optionFunc(value1).Select(value2 => resultFunc(value1, value2)));
        }

        public static Option<T> Where<T>(this Option<T> it, Func<T, bool> predicate)
        {
            return predicate(it.Value) ? it : new Option<T>();
        }
    }
}